#pragma once

#include "heapsort.h"
#include "mergesort.h"
#include "quicksort.h"
#include "stlsort.h"
#include "types.h"

#include <iostream>
#include <string>
#include <fstream>
#include <stdexcept>
#include <map>

#include <chrono>

#include <time.h>

#include "plot.h"

using namespace std;
using namespace std::chrono;

// gera uma lista de palavras a partir de um arquivo com palavras separadas por quebra de linha ou espaço
template<typename T>
inline vector<T> generateListFromFile(const string &filename)
{
  vector<T> list;

  ifstream file(filename);

  if (!file) {
    throw runtime_error("arquivo " + filename + " não encontrado ou não pode ser aberto!");
  }

  while (file) {
    T word;
    file >> word;
    list.push_back(word);
  }

  return list;
}

// executa o teste de um algoritmo, informando se ocorreu algum erro e informando o tempo que a ordenação levou
template<typename A, typename C, typename T>
inline double execute(const vector<T> &t)
{
  // obtem o instante antes da execução
  high_resolution_clock::time_point before = high_resolution_clock::now();

  // executa a ordenação
  vector<T> r = A(t,C()).sort();
  
  // obtem o instante depois da execução
  high_resolution_clock::time_point after = high_resolution_clock::now();

  // a diferença entre o depois e o antes é o tempo que a operação tomou
  duration<double> d = duration_cast<chrono::duration<double>>(after - before);

  // verifica se o vetor resultante foi mesmo ordenado
  //if (!assertSorted<T>(r,C())) {
  //  cerr << "a ordenação não foi executada corretamente!" << endl;
  //}

  return d.count();
}

template<typename T, typename C>
inline RoundResult executeAlgorithms(const vector<T> &v)
{
  RoundResult r {
    // executa o teste para o heapsort
    {"heap",execute<Heap<T>,C>(v)},

    // executa o teste para o mergesort
    {"merge",execute<Merge<T>,C>(v)},

    // executa o teste para o quicksort
    {"quick",execute<Quick<T>,C>(v)},

    // executa o teste para std::sort
    {"stl",execute<STLSort<T>,C>(v)}
  };
  
  return r;
}

inline vector<int> getNumberOfTests(int argc, char **argv)
{
  if (argc < 2) {
    throw runtime_error("uso: " + string(argv[0]) + " n1 n2 n3 n4 (...) nm");  
  }

  vector<int> r;

  for (int i = 1; i < argc; i++) {
    r.push_back(atoi(argv[i]));
  }

  return r;
}

template<typename T>
inline CaseResult executeCase(int n)
{
  high_resolution_clock::time_point before = high_resolution_clock::now();
  
  CaseResult r {
    // entrada aleatória, ordenação crescente
    {"rand-asc",executeAlgorithms<T,CompareMax<T>>(generateRandomData<T>(n))},
  
    // pior caso: já está ordenado em ordem decrescente
    {"desc-asc",executeAlgorithms<T,CompareMax<T>>(generateDescSortedData<T>(n))},

    // melhor caso: já está ordenado em ordem crescente
    {"asc-asc",executeAlgorithms<T,CompareMax<T>>(generateAscSortedData<T>(n))},

    // entrada aleatória, ordem decrescente
    {"rand-desc",executeAlgorithms<T,CompareMin<T>>(generateRandomData<T>(n))},

    // pior caso: já está ordenado em ordem crescente
    {"asc-desc",executeAlgorithms<T,CompareMin<T>>(generateAscSortedData<T>(n))},

    // melhor caso: já está ordenado em ordem decrescente
    {"desc-desc",executeAlgorithms<T,CompareMin<T>>(generateDescSortedData<T>(n))}
  };
  
  high_resolution_clock::time_point after = high_resolution_clock::now();
  
  duration<double> d = duration_cast<chrono::duration<double>>(after - before);
  
  cout << "a execução dos algoritmos para tamanho == " << n << " é " << d.count() << " segundos" << endl; 

  return r;
}

template<typename T>
inline FinalResult testSortGenerateData(const vector<int> &tests)
{
  FinalResult result;

  for (int i = 0; i < tests.size(); i++) {
    result[tests[i]] = executeCase<T>(tests[i]);
  }

  return result;
}

inline void showFinalResults(const FinalResult &results)
{
  for (FinalResult::const_iterator fit = results.begin(); fit != results.end(); fit++) {
    cout << fit->first << ":" << endl;
    for (CaseResult::const_iterator cit = fit->second.begin(); cit != fit->second.end(); cit++) {
      cout << "  " << cit->first << ":" << endl;
      for (RoundResult::const_iterator rit = cit->second.begin(); rit != cit->second.end(); rit++) {
        cout << "    " << rit->first << ": ";
        printf("%F",rit->second);
        cout << endl;
      } 
    } 
  } 
}

inline void plotFinalResults(const FinalResult &results, const vector<string> &cases, const vector<string> &algs, const string &filename)
{
  Plot plot(results,filename);
 
  for (const string &c: cases) {
    for (const string &a: algs) {
      plot.addCurve(a,c);   
    }
  }
  
  plot.buildLegend();

  cout << "Gráfico exportado para o arquivo " << filename << endl;
}

void exportFinalResultsToCSV(const FinalResult &results, const string &filename)
{

}
